package org.setec;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.setec.config.Aspect;
import org.setec.config.ConfigurationLoader;
import org.setec.config.InterpretableConfiguration;
import org.setec.config.JoinPoint;
import org.setec.utils.Utils;
import org.setec.utils.io.Persistance;

import java.lang.reflect.Method;

public class InstrumentMocker {

    private static InterpretableConfiguration configuration;

    public static InterpretableConfiguration getConfiguration() {
        return configuration;
    }

    public static void setConfiguration(InterpretableConfiguration interpretableConfiguration) {
        configuration = interpretableConfiguration;
    }

    public static void switchConfiguration(String path) {
        configuration = ConfigurationLoader.load(path);
    }

    /**
     * Method is allowed to execute if mode is LEARN, PASS, AUTO(no stub file)
     */
    public static boolean blockMethodExecution(String className, String methodName, Object[] args) {

        if (getConfiguration().isProcessRealMethod(className, methodName, Utils.objectClasses(args))) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * MOCK or AUTO(stub file)
     */
    public static Object getMethodResultSubstitution(String className, String methodName, Object[] args) {

        return Persistance.recall(getConfiguration().getStorage(), className, methodName, Utils.objectClasses(args));
    }

    public static Object processMethodResult(Object methodResult, String className, String methodName, Object[] args) {
        Object result = methodResult;
        if (getConfiguration().isLearn(className, methodName, Utils.objectClasses(args))) {
            Persistance.learn(getConfiguration().getStorage(), result, className, methodName, Utils.objectClasses(args));
        }
        if (getConfiguration().isMock(className, methodName, Utils.objectClasses(args))) {
            result = Persistance.recall(getConfiguration().getStorage(), className, methodName, Utils.objectClasses(args));
        }
        return result;
    }


    public static void instrumentClasses() throws Throwable {
        ClassPool classPool = ClassPool.getDefault();

        for( Aspect aspect : getConfiguration().getAspectList() ){
            CtClass ctClass = classPool.get(aspect.getClassName());
            for( JoinPoint joinPoint : aspect.getJoinPoints() ) {
                CtClass[] argClasses = new CtClass[joinPoint.getArgClassNames().length];
                for( int i = 0; i < joinPoint.getArgClassNames().length; i++ ){
                    argClasses[i] = classPool.get(joinPoint.getArgClassNames()[i]);
                }
                CtMethod ctMethod = ctClass.getDeclaredMethod(joinPoint.getMethodName(), argClasses);

                ctMethod.insertBefore("{ if( org.setec.InstrumentMocker.blockMethodExecution(\"" + aspect.getClassName() + "\"," +
                    "\"" + joinPoint.getMethodName() + "\",$args) ) return org.setec.InstrumentMocker.getMethodResultSubstitution(" +
                    "\"" + aspect.getClassName() + "\",\"" + joinPoint.getMethodName() + "\",$args); }");

               ctMethod.insertAfter(" { return org.setec.InstrumentMocker.processMethodResult(" +
                   "$_,\"" + aspect.getClassName() + "\",\"" + joinPoint.getMethodName() + "\",$args); } ");
            }
            ctClass.toClass();
        }
    }

}
